# File: advancedX.py
import os
from datetime import datetime
from typing import Union

import PyQt5.QtCore
import PyQt5.QtWidgets
import PyQt5.sip
import numpy
import pandas
from PyQt5 import Qt
from PyQt5.Qt import QToolButton
from PyQt5.QtWidgets import QAction

import autogen.advanced
import basicX
import common
import control_point_caculatorX
import datavisualX
import ezqt
import probe_calib_X
import record_path_X
from autogen import about
from real_time_status_X import RealTimeStatusX

pd = pandas


class AdvancedMeasurerTask(common.AbstractMeasurerTask):
    EPS_dict = {col: 5e-1 for col in common.ControlPoint.columns}

    def __init__(self, hm: common.HallMachine, trajectory: common.Trajectory,
                 recorder: Union[common.Recorder, common.RecorderSupportPushHM] = common.Recorder(),
                 timer: common.DataExchangerTimer = common.DEFAULT_MAIN_TIMER, ):

        super(AdvancedMeasurerTask, self).__init__()
        self.hm: common.HallMachine = hm
        self.hm_helper = common.HallMachineAxisHelper(self.hm)
        self.recorder = recorder
        self.mean_recorder = common.RecorderSupportPushHM(
            lambda: common.add_prefix(self.recorder.autogen_path_method(), prefix="mean."))
        self.timer: common.DataExchangerTimer = timer
        self.trajectory = trajectory
        # Assert no nan in traj
        assert not numpy.any(pandas.isna(self.trajectory.df))
        self.done = False
        # self.remain_records = 0
        self.total_records_here = 0
        self.i = 0
        n = 30
        self.autosave_timer = ezqt.FollowerTimer(n, self.timer)  # 主时钟超时n次后，启动自动保存任务
        self.__target_ctrl_pt = common.ControlPoint.from_hm(self.hm)
        self.__n_records = 0  # 表示在当前位置已经记录了几次

    def start(self):
        self.sig_mean_data_updated.emit(self.mean_recorder)
        self.sig_raw_data_updated.emit(self.recorder)
        self.timer.timeout.connect(self.__on_timer)
        self.autosave_timer.timeout_i.connect(self.__save_all)
        print("Measurer start")

    def __save_all(self):
        self.recorder.to_csv()
        self.mean_recorder.to_csv()

    def stop(self):
        self.timer.timeout.disconnect(self.__on_timer)
        self.autosave_timer.timeout_i.disconnect(self.__save_all)
        self.__save_all()
        self.done = True
        self.sig_mean_data_done.emit(self.mean_recorder)
        self.sig_raw_data_done.emit(self.recorder)

    def __on_timer(self):
        # 判断除了需要调整的量是否已经达到目标值
        def delta_too_small(status_delta: pandas.Series):
            print("Check whether delta is too small...")
            for col in common.ControlPoint.columns_all_except_records:
                if abs(status_delta[col]) > self.EPS_dict[col]:
                    return False
            return True

        current_status = common.ControlPoint.from_hm(self.hm)
        current_status.series[common.ControlPoint.kRecorders] = self.__n_records
        status_delta = self.__target_ctrl_pt.series - current_status.series
        stand_here_and_measure = status_delta[common.ControlPoint.kRecorders] > 0

        arrive_at_target = delta_too_small(
            status_delta) or self.hm_helper.all_axis_stopped()  # self.hm_helper.all_axis_stopped() and True
        if self.done or (len(self.trajectory) <= self.i and arrive_at_target and not stand_here_and_measure):
            print(
                "结束测量原因：done = %s\tlen(self.trajectory) = %s\tself.i = %s\tarrive_at_target = %s"
                % (self.done,
                   len(self.trajectory),
                   self.i,
                   arrive_at_target))
            return self.stop()
        if arrive_at_target:
            # if self.remain_records and not numpy.isnan(self.remain_records):
            if stand_here_and_measure:
                self.hm.update_all()
                self.recorder.push_back(self.hm, self.__n_records)
                self.mean_recorder.push_back(self.hm, self.__target_ctrl_pt.series[common.ControlPoint.kRecorders])
                self.__n_records += 1
            else:
                # if self.remain_records == 0:
                # assert self.__n_records >0
                self.mean_recorder.mean_last_n(self.__n_records)
                self.sig_mean_data_updated.emit(self.mean_recorder)
                self.sig_raw_data_updated.emit(self.recorder)
                print("发出更新信号，此时self.done=%s" % (self.done))

                self.__target_ctrl_pt = common.ControlPoint.from_Trajectory(self.trajectory, self.i)
                self.hm.set_to(*self.__target_ctrl_pt.series[self.__target_ctrl_pt.columns_xyz].tolist())
                self.i += 1
                self.__n_records = 0
        # if not nan_or_too_small(status_delta.kZ_mm, AdvancedMeasurerTask.EPS_DISTANCE):
        #     self.hm_helper.d_jog_to[common.Axis.Z](target_ctrl_pt[common.ControlPoint.kZ_mm])
        # if not nan_or_too_small(status_delta.kX_mm, AdvancedMeasurerTask.EPS_DISTANCE):
        #     self.hm_helper.d_jog_to[common.Axis.X](target_ctrl_pt[common.ControlPoint.kX_mm])
        # if not nan_or_too_small(status_delta.kY_mm, AdvancedMeasurerTask.EPS_DISTANCE):
        #     self.hm_helper.d_jog_to[common.Axis.Y](target_ctrl_pt[common.ControlPoint.kY_mm])


def gen_default_traj_for_record_background():
    records = 10
    traj = common.Trajectory()
    common.ControlPoint(0, 0, 0, records=records).add_into_traj(traj)
    common.ControlPoint(common.BOUNDINGS[common.Axis.X], 0, 0, records=records).add_into_traj(traj)
    common.ControlPoint(common.BOUNDINGS[common.Axis.X], common.BOUNDINGS[common.Axis.Y], 0,
                        records=records).add_into_traj(traj)
    common.ControlPoint(0, common.BOUNDINGS[common.Axis.Y], 0, records=records).add_into_traj(traj)
    common.ControlPoint(0, common.BOUNDINGS[common.Axis.Y], common.BOUNDINGS[common.Axis.Z],
                        records=records).add_into_traj(traj)
    common.ControlPoint(0, 0, common.BOUNDINGS[common.Axis.Z],
                        records=records).add_into_traj(traj)
    common.ControlPoint(common.BOUNDINGS[common.Axis.X], 0, common.BOUNDINGS[common.Axis.Z],
                        records=records).add_into_traj(traj)
    common.ControlPoint(common.BOUNDINGS[common.Axis.X], common.BOUNDINGS[common.Axis.Y],
                        common.BOUNDINGS[common.Axis.Z],
                        records=records).add_into_traj(traj)
    return traj


class AdvancedX(autogen.advanced.Ui_AdvancedMainWindow):
    def __init__(self, parent: Qt.QMainWindow, hm: common.HallMachine,
                 main_timer=common.DEFAULT_MAIN_TIMER):
        super(AdvancedX, self).__init__()
        self.__temp_dir = common.TEMP_DIR
        self.__ctrl_pts_csv_path = os.path.join(self.__temp_dir, "control_points.csv")
        self.__parent = parent
        self.setupUi(self.__parent)
        self.__icon = PyQt5.Qt.QIcon(os.path.join(common.QT_GUI_PATH, "resource/logo.ico"))
        self.hm = hm
        self.hm_helper = common.HallMachineAxisHelper(self.hm)
        self.main_timer = main_timer
        self.__init_docks()
        self.__init_menu_bar()
        self.__init_record_button()
        self.__init_ctrl_pts_table()
        self.__show_last_update()

        self.connect_all()
        self.measurer: AdvancedMeasurerTask = None

    def connect_all(self):
        self.action_about.triggered.connect(self.__on_about)
        self.dockWidget_Basic.visibilityChanged.connect(self.__on_basic_visibility_changed)
        self.pushButton_ctrl_pts_calculator.clicked.connect(self.__on_open_ctrl_pts_calculator)
        self.pushButton_stop.clicked.connect(self.__on_stop)
        self.pushButton_measure.clicked.connect(
            self.__on_measure)
        self.main_timer.sig_insert_control_pts.connect(self.__on_ctrl_pts_send_from_calculator)
        self.pushButton_tar_traj_visual.clicked.connect(self.__on_plot_traj)
        self.pushButton_probe_calib.clicked.connect(self.__on_push_probe_calibration)
        self.pushButton_sendcmd.clicked.connect(lambda: self.hm.send_cmd(self.plainTextEdit_cmd.toPlainText()))

    # def __on_push_send_cmd(self):
    #     cmd = self.plainTextEdit_cmd.toPlainText()
    #     self.hm.send_cmd(cmd)

    def __on_about(self):
        self.__about_window = Qt.QMainWindow()
        about.Ui_MainWindow().setupUi(self.__about_window)
        self.__about_window.show()

    def __on_measure(self):
        self.__on_measure_or_record_background_along_ctrl_pts_in_table(common.Purpose.MEASUREMENT)

    def __on_push_probe_calibration(self):
        __probe_calib_window = Qt.QMainWindow()
        __probe_calib_window.setCentralWidget(Qt.QWidget())
        self.__prob_calib_X = probe_calib_X.ProbeCalibX(self.hm, __probe_calib_window.centralWidget(), self.main_timer)
        __probe_calib_window.show()
        self.__probe_calib_window = __probe_calib_window

    def __show_last_update(self):
        t_vtob = datetime.fromtimestamp(os.path.getmtime(common.DefaultFilePath.mean_VtoB)).strftime("%Y-%m-%d %H:%M")
        t_vback = datetime.fromtimestamp(os.path.getmtime(common.DefaultFilePath.mean_VBackground)).strftime(
            "%Y-%m-%d %H:%M")
        self.label_updatetime_of_background_file.setText(t_vback)
        self.label_last_update_v_to_B.setText(t_vtob)

    def __init_menu_bar(self):
        self.action_set_control_pts.triggered.connect(lambda: self.dockWidget_set_control_pts.setVisible(True))
        self.action_basic.triggered.connect(lambda: self.dockWidget_Basic.setVisible(True))
        self.action_data_visual.triggered.connect(lambda: self.dockWidget_graphic.setVisible(True))
        self.action_status_platte.triggered.connect(lambda: self.dockWidget_RT_status_and_record.setVisible(True))

    def __on_open_ctrl_pts_calculator(self):
        window = Qt.QMainWindow()
        window.setWindowIcon(self.__icon)
        self.__calculatorX = control_point_caculatorX.ControlPointCalculatorX(window, self.hm, self.main_timer)
        window.show()

    def __init_docks(self):
        self.dockWidget_Basic = PyQt5.QtWidgets.QDockWidget(self.__parent)
        self.dockWidget_Basic.setWidget(Qt.QWidget(self.dockWidget_Basic))
        self.dockWidget_Basic.setWindowTitle("基础")
        self.dockWidget_RT_status_and_record = PyQt5.QtWidgets.QDockWidget(self.__parent)
        self.dockWidget_RT_status_and_record.setWindowTitle("实时数据")
        widget = Qt.QWidget(self.dockWidget_RT_status_and_record)
        self.dockWidget_RT_status_and_record.setWidget(widget)
        vbox_layout = Qt.QVBoxLayout(widget)
        w1 = Qt.QWidget(self.dockWidget_RT_status_and_record)
        w2 = Qt.QWidget(self.dockWidget_RT_status_and_record)
        vbox_layout.addWidget(w1)
        vbox_layout.addWidget(w2)
        self.__RT_status = RealTimeStatusX(w1, self.hm, self.main_timer)
        self.__recorder_path_ui = record_path_X.RecordPathX(w2, )
        self.__parent.addDockWidget(
            PyQt5.QtCore.Qt.LeftDockWidgetArea, self.dockWidget_Basic)
        self.__parent.addDockWidget(PyQt5.QtCore.Qt.RightDockWidgetArea, self.dockWidget_graphic)
        self.__parent.addDockWidget(PyQt5.QtCore.Qt.LeftDockWidgetArea, self.dockWidget_set_control_pts)
        self.__parent.addDockWidget(PyQt5.QtCore.Qt.LeftDockWidgetArea, self.dockWidget_RT_status_and_record)
        self.__parent.splitDockWidget(self.dockWidget_Basic, self.dockWidget_RT_status_and_record,
                                      PyQt5.QtCore.Qt.Vertical)
        self.__parent.tabifyDockWidget(self.dockWidget_Basic, self.dockWidget_set_control_pts)
        self.__data_viwer = datavisualX.DataVisualX(self.hm, self.dockWidget_graphic.widget(), self.main_timer, None)
        self.__basicX = basicX.BasicX(self.hm, self.main_timer, self.dockWidget_Basic.widget())

    def __on_basic_visibility_changed(self, visibility):
        if visibility:
            self.__basicX.set_data_visualX(self.__data_viwer)

    def __fill_empty_on_table_widget(self):
        traj = common.Trajectory()
        traj.append(ezqt.QTableWidget_to_df(self.tableWidget))
        traj.fill_empty_or_na(self.hm_helper)
        ezqt.df_to_QTableWidget_with_header(traj.df, self.tableWidget)
        return traj

    def __on_plot_traj(self):
        traj = self.__fill_empty_on_table_widget()
        self.main_timer.sig_plot_control_pts.emit(traj)

    def __on_ctrl_pts_send_from_calculator(self, traj: common.Trajectory):
        df = ezqt.QTableWidget_to_df(self.tableWidget)
        df = pandas.concat([df, traj.df], ignore_index=True)
        ezqt.df_to_QTableWidget_with_header(df, self.tableWidget)

    def __init_ctrl_pts_table(self):
        ezqt.df_to_QTableWidget_with_header(common.Trajectory().df, self.tableWidget)
        self.__connect_insert_and_drop()
        self.__connect_edit_csv_in_excel()
        self.pushButton_refresh.clicked.connect(self.__on_refresh_tableWidget)

    def __init_record_button(self):
        self.toolButton_record_background.setPopupMode(QToolButton.InstantPopup)
        txt_use_default = "采用默认的本底测量轨迹"
        txt_user_define = "采用自定义的本底测量轨迹"
        action_use_default = QAction(txt_use_default, self.toolButton_record_background)
        action_use_ctrl_pts_in_table = QAction(txt_user_define, self.toolButton_record_background)
        self.__methods = {txt_use_default: common.Method.DEFAULT, txt_user_define: common.Method.USER_DEFINE}
        self.toolButton_record_background.addActions([action_use_default, action_use_ctrl_pts_in_table])
        action_use_default.triggered.connect(self.__on_record_back_ground_along_default)
        action_use_ctrl_pts_in_table.triggered.connect(
            lambda: self.__on_measure_or_record_background_along_ctrl_pts_in_table(common.Purpose.RECORD_BACKGROUND))

    def __set_irrelevant_disabled(self, on: bool):
        self.tableWidget.setDisabled(on)
        self.pushButton_measure.setDisabled(on)
        self.toolButton_record_background.setDisabled(on)
        self.pushButton_probe_calib.setDisabled(on)
        self.pushButton_refresh.setDisabled(on)
        self.pushButton_edit_ctrl_pts.setDisabled(on)
        self.pushButton_insert.setDisabled(on)
        self.pushButton_drop.setDisabled(on)

    def __on_measure_or_record_background_along_ctrl_pts_in_table(self, purpose: common.Purpose):
        df_ctrl_pts = ezqt.QTableWidget_to_df(self.tableWidget)
        df_ctrl_pts.to_csv(self.__ctrl_pts_csv_path, index=False)
        traj = self.__fill_empty_on_table_widget()  # No nan
        recorder = None
        if purpose == common.Purpose.MEASUREMENT:
            recorder = common.Recorder(
                common.Recorder.DEFAULT_RECORD_DIR,
                common.Recorder.DEFAULT_PREFIX)  # 必须传参数，而非采用默认值，否则运行时对两个DEFAULT值的修改不会影响新对象
        elif purpose == common.Purpose.RECORD_BACKGROUND:
            recorder = common.RecorderSupportPushHM(lambda: common.DefaultFilePath.raw_VBackground)
        self.measurer = AdvancedMeasurerTask(
            self.hm,
            traj,
            recorder,
            self.main_timer)
        self.__data_viwer.set_measurer(self.measurer)
        self.__set_irrelevant_disabled(True)
        self.measurer.sig_raw_data_done.connect(self.__on_measurer_done)
        self.measurer.sig_mean_data_updated.connect(self.__select_current_loc_on_table)
        self.measurer.start()

    def __select_current_loc_on_table(self, mean_recorder: common.Recorder):
        row = len(mean_recorder.df)
        if row < self.tableWidget.rowCount():
            self.tableWidget.selectRow(row)

    def __on_measurer_done(self, record_raw: common.RecorderBase):
        self.__set_irrelevant_disabled(False)
        self.measurer = None

    def __on_record_back_ground_along_default(self):
        traj = gen_default_traj_for_record_background()
        ezqt.df_to_QTableWidget_with_header(traj.df, self.tableWidget)
        self.__on_measure_or_record_background_along_ctrl_pts_in_table(common.Purpose.RECORD_BACKGROUND)

    def __on_stop(self):
        for axis in self.hm_helper.d_stop:
            self.hm_helper.d_stop[axis]()
        if self.measurer:
            self.measurer.stop()
            self.__on_measurer_done(None)

    def __connect_insert_and_drop(self):
        self.pushButton_drop.clicked.connect(lambda: ezqt.drop_selected_rows(self.tableWidget))
        self.pushButton_insert.clicked.connect(lambda: ezqt.insert(self.tableWidget))

    def __connect_edit_csv_in_excel(self):
        def write_and_open_temp_file():
            file_path = self.__ctrl_pts_csv_path
            df = ezqt.QTableWidget_to_df(self.tableWidget)
            df.to_csv(file_path, index=False)
            cmd = "start %s" % file_path
            os.system(cmd)

        self.pushButton_edit_ctrl_pts.clicked.connect(write_and_open_temp_file)

    def __on_refresh_tableWidget(self):
        file_path = self.__ctrl_pts_csv_path
        if not os.path.exists(self.__ctrl_pts_csv_path):
            return
        else:
            df = pd.read_csv(file_path)
            ezqt.df_to_QTableWidget_with_header(df, self.tableWidget)


if __name__ == '__main__':
    from PyQt5.Qt import QApplication
    import sys

    hm = common.HallMachine()
    hm.open()
    app = QApplication([])
    main_window = PyQt5.Qt.QMainWindow()
    advance_ui = AdvancedX(main_window, hm)
    advance_ui.main_timer.start()
    main_window.show()
    sys.exit(app.exec_())
